//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// 'Transform' tool.
//

# include "StdAfx.h"
# include "Resource.h"
# include "ToolTransform.h"
# include "UndoRedo.h"
# include "TransformDefFile.h"
# include "Map.h"
# include "MapView.h"
# include "Scale.h"
# include "Position.h"
# include "Tool.h"
# include "MathAlgo.h"
# include "Settings.h"
# include "fast_floor.h"
# include "Button.h"
# include "Dialog.h"
# include "Globals.h"

const int c_AB_SIZE = 150;

static size_t g_cPointIndex;
static ptpairs_t g_PtPairs;

static size_t g_cTransformationOrder = 0;

//////////////////////////////////

class CAffineTransformationAction : public CEditAction {
  public:
	CAffineTransformationAction (const aff_transform_t & _at) : m_at (_at) {
		m_at.Reverse (m_atReverse);

		// Save points pairs.
		m_PtPairs = g_PtPairs;
	}

	virtual void GetName (string_t & _str) const {_str.append (GetStr (IDS_AFFINE));}
	virtual cu_t CanUndo () const {return cuYes;}
	virtual bool Do ();
	virtual void Undo ();
	virtual void Redo () {CAffineTransformationAction::Do ();}

  private:
	const aff_transform_t m_at;
	aff_transform_t m_atReverse;

	ptpairs_t m_PtPairs;
};

bool CAffineTransformationAction::Do () {
	CCritSec::CLock lock (g_cs);

	g_map.Transform (m_at);

	for (size_t cPoint = 0; cPoint < 3 && cPoint < g_PtPairs.size (); ++ cPoint) {
		if (g_PtPairs [cPoint].bDefined) {
			m_at.ApplyTo (g_PtPairs [cPoint].ptFrom);
			assert (
				::fabs (g_PtPairs [cPoint].ptFrom.x - g_PtPairs [cPoint].ptTo.x) < 3e-5 &&
				::fabs (g_PtPairs [cPoint].ptFrom.y - g_PtPairs [cPoint].ptTo.y) < 3e-5
			);
		}
	}

	return true;
}

void CAffineTransformationAction::Undo () {
	CCritSec::CLock lock (g_cs);

	g_map.Transform (m_atReverse);

	// Restore points pairs.
	g_PtPairs = m_PtPairs;
}

static inline
void AffTransformMap (const aff_transform_t & _at) {
	PerformEditAction (new CAffineTransformationAction (_at));

	RefreshScales ();
	Refresh ();
}

//////////////////////////////////

class CQuadraticTransformationAction : public CEditAction {
  public:
	CQuadraticTransformationAction (const q_transform_t & _qt) : m_qt (_qt)	{
		// Save points pairs.
		m_PtPairs = g_PtPairs;
	}

	virtual void GetName (string_t & _str) const {_str.append (GetStr (IDS_QUADRATIC));}
	virtual cu_t CanUndo () const {return cuBackup;}
	virtual bool Do ();
	virtual void Undo ();
	virtual void Redo ();

  private:
	const q_transform_t m_qt;

	ptpairs_t m_PtPairs;
};

bool CQuadraticTransformationAction::Do () {
	CCritSec::CLock lock (g_cs);

	g_map.Transform (m_qt);

	for (size_t cPoint = 0; cPoint < 6 && cPoint < g_PtPairs.size (); ++ cPoint) {
		if (g_PtPairs [cPoint].bDefined && cPoint < g_PtPairs.size ()) {
			m_qt.ApplyTo (g_PtPairs [cPoint].ptFrom);
			assert (
				::fabs (g_PtPairs [cPoint].ptFrom.x - g_PtPairs [cPoint].ptTo.x) < 3e-5 &&
				::fabs (g_PtPairs [cPoint].ptFrom.y - g_PtPairs [cPoint].ptTo.y) < 3e-5
			);
		}
		// TODO: warning about lost precision.
	}

	return true;
}

void CQuadraticTransformationAction::Undo () {
	// Restore points pairs.
	g_PtPairs = m_PtPairs;
}

void CQuadraticTransformationAction::Redo () {
	// Restore points pairs.
	g_PtPairs = m_PtPairs;

	// Apply point pairs.
	for (size_t cPoint = 0; cPoint < 6 && cPoint < g_PtPairs.size (); ++ cPoint) {
		if (g_PtPairs [cPoint].bDefined && cPoint < g_PtPairs.size ()) {
			m_qt.ApplyTo (g_PtPairs [cPoint].ptFrom);
			assert (
				::fabs (g_PtPairs [cPoint].ptFrom.x - g_PtPairs [cPoint].ptTo.x) < 3e-5 &&
				::fabs (g_PtPairs [cPoint].ptFrom.y - g_PtPairs [cPoint].ptTo.y) < 3e-5
			);
		}
	}
}

static inline
void QTransformMap (const q_transform_t & _qt) {
	PerformEditAction (new CQuadraticTransformationAction (_qt));

	RefreshScales ();
	Refresh ();
}

//////////////////////////////////

# include "MTATransform.h"

class CMTATransformationAction : public CEditAction {
  public:
	CMTATransformationAction (const mta_transform_t & _tt) : m_tt (_tt) {
		// Save points pairs.
		m_PtPairs = g_PtPairs;
	}

	virtual void GetName (string_t & _str) const {_str.append (GetStr (IDS_MTA));}
	virtual cu_t CanUndo () const {return cuBackup;}
	virtual bool Do ();
	virtual void Undo ();
	virtual void Redo ();

  private:
	const mta_transform_t m_tt;
	ptpairs_t m_PtPairs;
};

bool CMTATransformationAction::Do () {
	CCritSec::CLock lock (g_cs);

	g_map.Transform (m_tt);

	for (size_t cPoint = 0; cPoint < g_PtPairs.size (); ++ cPoint) {
		if (g_PtPairs [cPoint].bDefined) {
			m_tt.ApplyTo (g_PtPairs [cPoint].ptFrom);
			assert (
				::fabs (g_PtPairs [cPoint].ptFrom.x - g_PtPairs [cPoint].ptTo.x) < 3e-5 &&
				::fabs (g_PtPairs [cPoint].ptFrom.y - g_PtPairs [cPoint].ptTo.y) < 3e-5
			);
		}
	}

	return true;
}

void CMTATransformationAction::Undo () {
	// Restore points pairs.
	g_PtPairs = m_PtPairs;
}

void CMTATransformationAction::Redo () {
	// Restore points pairs.
	g_PtPairs = m_PtPairs;

	// Apply point pairs.
	for (size_t cPoint = 0; cPoint < g_PtPairs.size (); ++ cPoint) {
		if (g_PtPairs [cPoint].bDefined) {
			m_tt.ApplyTo (g_PtPairs [cPoint].ptFrom);
			assert (
				::fabs (g_PtPairs [cPoint].ptFrom.x - g_PtPairs [cPoint].ptTo.x) < 3e-5 &&
				::fabs (g_PtPairs [cPoint].ptFrom.y - g_PtPairs [cPoint].ptTo.y) < 3e-5
			);
		}
	}
}

static inline
void MTATransformMap (const mta_transform_t & _tt) {
	PerformEditAction (new CMTATransformationAction (_tt));

	RefreshScales ();
	Refresh ();
}

/////////////////////////////////

static HCURSOR g_hcurTransform [6];
static HCURSOR g_hcurTransformDrag;

static HPEN g_hRedThinPen;
static HPEN g_hDkRedThinPen;

/////////////////////////////////

static
void CalculateTransform () {
	switch (g_cTransformationOrder) {
		case 0: {
			aff_transform_t at;
			at.fAXx = 1;
			at.fAXy = 0;
			at.fAYx = 0;
			at.fAYy = 1;
			at.fBX = g_PtPairs [0].ptTo.x - g_PtPairs [0].ptFrom.x;
			at.fBY = g_PtPairs [0].ptTo.y - g_PtPairs [0].ptFrom.y;
			AffTransformMap (at);
			break;
		}

		case 1: {
			assert (g_PtPairs.size () >= 3);

			aff_transform_t at;
			float fX_To [3], fY_To [3];
			for (size_t c = 0; c < ARRAY_SIZE (fX_To); ++ c) {
				fX_To [c] = g_PtPairs [c].ptTo.x;
				fY_To [c] = g_PtPairs [c].ptTo.y;
			}
			const point_t ptFrom [3] = {g_PtPairs [0].ptFrom, g_PtPairs [1].ptFrom, g_PtPairs [2].ptFrom};
			if (
				! Solve3x3 (ptFrom, fX_To, at.fAXx, at.fAXy, at.fBX) ||
				! Solve3x3 (ptFrom, fY_To, at.fAYx, at.fAYy, at.fBY)
			) {
				ErrorMsgBox ("The transformation is impossible:\ncontradictory input data.");
				return;
			}
			AffTransformMap (at);
			break;
		}

		case 2: {
			assert (g_PtPairs.size () >= 6);

			CLinearEquationSystem esX (6);
			CLinearEquationSystem esY (6);
			for (size_t cPoint = 0; cPoint < 6; ++ cPoint) {
				const float xFrom = g_PtPairs [cPoint].ptFrom.x;
				const float yFrom = g_PtPairs [cPoint].ptFrom.y;

				esX.Matrix (0,  cPoint) = xFrom*xFrom;
				esX.Matrix (1,  cPoint) = xFrom*yFrom;
				esX.Matrix (2,  cPoint) = yFrom*yFrom;
				esX.Matrix (3,  cPoint) = xFrom;
				esX.Matrix (4,  cPoint) = yFrom;
				esX.Matrix (5,  cPoint) = 1;
				esX.FreeVector (cPoint) = g_PtPairs [cPoint].ptTo.x;

				esY.Matrix (0,  cPoint) = xFrom*xFrom;
				esY.Matrix (1,  cPoint) = xFrom*yFrom;
				esY.Matrix (2,  cPoint) = yFrom*yFrom;
				esY.Matrix (3,  cPoint) = xFrom;
				esY.Matrix (4,  cPoint) = yFrom;
				esY.Matrix (5,  cPoint) = 1;
				esY.FreeVector (cPoint) = g_PtPairs [cPoint].ptTo.y;
			}
			if (
				esX.Solve () ||
				esY.Solve ()
			) {
				ErrorMsgBox ("The transformation is impossible:\ncontradictory input data.");
				return;
			}

			q_transform_t qt;
			qt.fDXxx = esX.Solution (0);
			qt.fDXxy = esX.Solution (1);
			qt.fDXyy = esX.Solution (2);
			qt.fAXx  = esX.Solution (3);
			qt.fAXy  = esX.Solution (4);
			qt.fBX   = esX.Solution (5);
			qt.fDYxx = esY.Solution (0);
			qt.fDYxy = esY.Solution (1);
			qt.fDYyy = esY.Solution (2);
			qt.fAYx  = esY.Solution (3);
			qt.fAYy  = esY.Solution (4);
			qt.fBY   = esY.Solution (5);


			QTransformMap (qt);
			break;
		}

		case 3:	{
			mta_transform_t tt;

			for (size_t cPoint = 0; cPoint < g_PtPairs.size (); ++ cPoint)
				if (g_PtPairs [cPoint].bDefined)
					tt.PushPair (g_PtPairs [cPoint].ptFrom, g_PtPairs [cPoint].ptTo);

			if (! tt.Calculate ()) {
				ErrorMsgBox ("The transformation is impossible:\ncontradictory input data.");
				break;
			}
			MTATransformMap (tt);

			break;
		}

		default:
			assert (false);
	}
}

class CTransformDlg : public CDialog {
  public:
	CTransformDlg (UINT _uiDlg) : CDialog (_uiDlg) {}

	virtual void UpdateCtrls (bool _bDeep = false);

	// Override.
	virtual bool OnInit (HWND _hwndFocus) {
		::SetWindowPos (m_hWnd, NULL, GetSetting ("TransformX", (DWORD) 0), GetSetting ("TransformY", (DWORD) 0), 0, 0, SWP_NOZORDER | SWP_NOSIZE);

		SetButtonIcon (m_hWnd, IDC_DELETE_POINT1, IDI_CROSS);
		SetButtonIcon (m_hWnd, IDC_DELETE_POINT2, IDI_CROSS);
		SetButtonIcon (m_hWnd, IDC_DELETE_POINT3, IDI_CROSS);
		SetButtonIcon (m_hWnd, IDC_DELETE_POINT4, IDI_CROSS);
		SetButtonIcon (m_hWnd, IDC_DELETE_POINT5, IDI_CROSS);
		SetButtonIcon (m_hWnd, IDC_DELETE_POINT6, IDI_CROSS);

		SetButtonIcon (m_hWnd, IDC_LOCATE_POINT1, IDI_LOCATE_SMALL);
		SetButtonIcon (m_hWnd, IDC_LOCATE_POINT2, IDI_LOCATE_SMALL);
		SetButtonIcon (m_hWnd, IDC_LOCATE_POINT3, IDI_LOCATE_SMALL);
		SetButtonIcon (m_hWnd, IDC_LOCATE_POINT4, IDI_LOCATE_SMALL);
		SetButtonIcon (m_hWnd, IDC_LOCATE_POINT5, IDI_LOCATE_SMALL);
		SetButtonIcon (m_hWnd, IDC_LOCATE_POINT6, IDI_LOCATE_SMALL);

		SetButtonIcon (m_hWnd, IDC_LOAD, IDI_LOAD);
		SetButtonIcon (m_hWnd, IDC_SAVE, IDI_SAVE);

		return true;
	}

	virtual void OnCommand (WORD _wCmd, WORD _wNotifyCode, HWND _hwndCtl) {
		_RestoreFocus ();

		switch (_wCmd) {
			case IDC_POINT1: g_cPointIndex = 0; _UpdateCursor (); return;
			case IDC_POINT2: g_cPointIndex = 1; _UpdateCursor (); return;
			case IDC_POINT3: g_cPointIndex = 2; _UpdateCursor (); return;
			case IDC_POINT4: g_cPointIndex = 3; _UpdateCursor (); return;
			case IDC_POINT5: g_cPointIndex = 4; _UpdateCursor (); return;
			case IDC_POINT6: g_cPointIndex = 5; _UpdateCursor (); return;

			case IDC_DELETE_POINT1: g_PtPairs [0].bDefined = false; UpdateCtrls (); Redraw (); return;
			case IDC_DELETE_POINT2: g_PtPairs [1].bDefined = false; UpdateCtrls (); Redraw (); return;
			case IDC_DELETE_POINT3: g_PtPairs [2].bDefined = false; UpdateCtrls (); Redraw (); return;
			case IDC_DELETE_POINT4: g_PtPairs [3].bDefined = false; UpdateCtrls (); Redraw (); return;
			case IDC_DELETE_POINT5: g_PtPairs [4].bDefined = false; UpdateCtrls (); Redraw (); return;
			case IDC_DELETE_POINT6: g_PtPairs [5].bDefined = false; UpdateCtrls (); Redraw (); return;

			case IDC_LOCATE_POINT1:	if (g_PtPairs [0].bDefined) SetToCenter (g_PtPairs [0].ptFrom); Refresh ();	return;
			case IDC_LOCATE_POINT2: if (g_PtPairs [1].bDefined) SetToCenter (g_PtPairs [1].ptFrom); Refresh ();	return;
			case IDC_LOCATE_POINT3: if (g_PtPairs [2].bDefined) SetToCenter (g_PtPairs [2].ptFrom); Refresh ();	return;
			case IDC_LOCATE_POINT4: if (g_PtPairs [3].bDefined) SetToCenter (g_PtPairs [3].ptFrom); Refresh ();	return;
			case IDC_LOCATE_POINT5: if (g_PtPairs [4].bDefined) SetToCenter (g_PtPairs [4].ptFrom); Refresh ();	return;
			case IDC_LOCATE_POINT6: if (g_PtPairs [5].bDefined) SetToCenter (g_PtPairs [5].ptFrom); Refresh ();	return;

			default:
				ProcessCommonCmd (_wCmd);
				break;
		}
	}

	void ProcessCommonCmd (WORD _wCmd);

	virtual void OnMove (int _x, int _y) {
		RECT rect;
		::GetWindowRect (m_hWnd, & rect);
		POINT p = {rect.left, rect.top};
		::ScreenToClient (g_hMapWnd, & p);
		if (p.x < 0)
			p.x = 0;
		if (p.y < 0)
			p.y = 0;

		SaveSetting ("TransformX", DWORD (p.x));
		SaveSetting ("TransformY", DWORD (p.y));
	}

	virtual void OnClose () {
		CTool::SetCurrentTool (toolDefault);

		_RestoreFocus ();
	}

  protected:
	// Restore focus to keep keyboard input (accelerators) in main window.
	void _RestoreFocus ();

	void _UpdateCursor ();
	void _Update ();
};

void CTransformDlg::_RestoreFocus () {
	::SetFocus (NULL);
	::SetFocus (g_hWnd);
}

void CTransformDlg::_UpdateCursor () {
	CTool::SetPermanentCursor (g_hcurTransform [g_cPointIndex < ARRAY_SIZE (g_hcurTransform) ? g_cPointIndex : 0]);
	Redraw ();
}

void CTransformDlg::_Update () {
	UpdateCtrls ();
	_UpdateCursor ();
}

void CTransformDlg::UpdateCtrls (bool _bDeep) {
	switch (g_cPointIndex) {
		default:
		case 0: ::CheckRadioButton (m_hWnd, IDC_POINT1, IDC_POINT6, IDC_POINT1); break;
		case 1: ::CheckRadioButton (m_hWnd, IDC_POINT1, IDC_POINT6, IDC_POINT2); break;
		case 2: ::CheckRadioButton (m_hWnd, IDC_POINT1, IDC_POINT6, IDC_POINT3); break;
		case 3: ::CheckRadioButton (m_hWnd, IDC_POINT1, IDC_POINT6, IDC_POINT4); break;
		case 4: ::CheckRadioButton (m_hWnd, IDC_POINT1, IDC_POINT6, IDC_POINT5); break;
		case 5: ::CheckRadioButton (m_hWnd, IDC_POINT1, IDC_POINT6, IDC_POINT6); break;
	}

	EnableDlgItem (IDC_LOCATE_POINT1, g_PtPairs [0].bDefined);
	EnableDlgItem (IDC_DELETE_POINT1, g_PtPairs [0].bDefined);

	EnableDlgItem (IDC_LOCATE_POINT2, g_PtPairs [1].bDefined);
	EnableDlgItem (IDC_DELETE_POINT2, g_PtPairs [1].bDefined);

	EnableDlgItem (IDC_LOCATE_POINT3, g_PtPairs [2].bDefined);
	EnableDlgItem (IDC_DELETE_POINT3, g_PtPairs [2].bDefined);

	EnableDlgItem (IDC_LOCATE_POINT4, g_PtPairs [3].bDefined);
	EnableDlgItem (IDC_DELETE_POINT4, g_PtPairs [3].bDefined);

	EnableDlgItem (IDC_LOCATE_POINT5, g_PtPairs [4].bDefined);
	EnableDlgItem (IDC_DELETE_POINT5, g_PtPairs [4].bDefined);

	EnableDlgItem (IDC_LOCATE_POINT6, g_PtPairs [5].bDefined);
	EnableDlgItem (IDC_DELETE_POINT6, g_PtPairs [5].bDefined);

	switch (g_cTransformationOrder) {
		case 0: EnableDlgItem (IDC_APPLY, g_PtPairs [0].bDefined); break;
		case 1: EnableDlgItem (IDC_APPLY, g_PtPairs [0].bDefined && g_PtPairs [1].bDefined && g_PtPairs [2].bDefined); break;
		case 2: 
			EnableDlgItem (
				IDC_APPLY,
				g_PtPairs [0].bDefined && g_PtPairs [1].bDefined && g_PtPairs [2].bDefined &&
				g_PtPairs [3].bDefined && g_PtPairs [4].bDefined && g_PtPairs [5].bDefined
			);
			break;
	}
}

void CTransformDlg::ProcessCommonCmd (WORD _wCmd) {
	switch (_wCmd) {
		case IDC_APPLY: CalculateTransform (); return;

		case IDC_SAVE:
			SaveTransformDefFile (g_PtPairs);
			return;

		case IDC_LOAD:
			if (! LoadTransformDefFile (g_PtPairs)) {
				const size_t cSize = g_PtPairs.size ();
				for (size_t c = 0; c < cSize; ++ c) {
					const ptpair_t & pp = g_PtPairs [c];
					if (pp.bDefined)
						if (
							! g_map.rectBound.Contains (pp.ptFrom) ||
							! g_map.rectBound.Contains (pp.ptTo)
						) {
							ErrorMsgBox ("Some of points' pairs are out of map!");
							break;
						}
				}

				if (g_cPointIndex >= g_PtPairs.size ())
					g_cPointIndex = 0;

				UpdateCtrls (true);
				Redraw ();
			}
			return;
	}
}

/////////////////////////////////

# include "ComboBox.h"

class CTransformMultipleDlg : public CTransformDlg {
  public:
	CTransformMultipleDlg () : CTransformDlg (IDD_TRANSFORM_MTA), m_hCB (NULL) {}

	// Override.
	virtual bool OnInit (HWND _hwndFocus) {
		m_hCB = GetDlgItem (IDC_POINT);
		UpdateCtrls (true);

		return CTransformDlg::OnInit (_hwndFocus);
	}

	virtual void OnCommand (WORD _wCmd, WORD _wNotifyCode, HWND _hwndCtl) {
		if (_wCmd != IDC_POINT) // NOTE: avoid _RestoreFocus() since it blocks opening combo's list.
			_RestoreFocus ();

		switch (_wCmd) {
			case IDC_POINT:
				if (_wNotifyCode == CBN_SELCHANGE) {
					int nIndex = 0;
					GetComboBoxItemData (m_hWnd, IDC_POINT, nIndex);

					g_cPointIndex = nIndex;
					_Update ();
				}
				break;

			case IDC_ADD:
				g_PtPairs.push_back (ptpair_t ());
				g_PtPairs.back ().bDefined = false;

				g_cPointIndex = g_PtPairs.size () - 1;

				_UpdateCB ();
				_Update ();
				break;

			case IDC_BEGIN: g_cPointIndex = 0; _Update (); break;
			case IDC_END:   g_cPointIndex = g_PtPairs.size () - 1; _Update (); break;
			case IDC_PREV:  if (g_cPointIndex > 0) -- g_cPointIndex; _Update (); break;
			case IDC_NEXT:  if (g_cPointIndex + 1 < g_PtPairs.size ()) ++ g_cPointIndex; _Update (); break;

			case IDC_DELETE_POINT1:
				if (g_cPointIndex < g_PtPairs.size ()) {
					g_PtPairs [g_cPointIndex].bDefined = false;
					UpdateCtrls ();
					Redraw ();
				}
				break;

			case IDC_LOCATE_POINT1:
				if (g_cPointIndex < g_PtPairs.size () && g_PtPairs [g_cPointIndex].bDefined) {
					SetToCenter (g_PtPairs [g_cPointIndex].ptFrom);
					Refresh ();
				}
				break;

			default:
				ProcessCommonCmd (_wCmd);
				break;
		}
	}

  private:
	HWND m_hCB;
	void _UpdateCB () {
		::SendMessage (m_hCB, CB_RESETCONTENT, 0, 0);
		const size_t cPairs = g_PtPairs.size ();
		for (size_t cItem = 0; cItem < cPairs; ++ cItem) {
			char strItem [10];
			::sprintf (strItem, "%d", cItem + 1);
			AddComboBox (m_hCB, cItem, cItem, strItem);
		}
	}

	virtual void UpdateCtrls (bool _bDeep = false);
};

void CTransformMultipleDlg::UpdateCtrls (bool _bDeep) {
	// Combo.
	if (_bDeep)
		_UpdateCB ();
	SelectComboBox (m_hCB, g_cPointIndex);

	// Prev/Next
	EnableDlgItem (IDC_PREV, g_cPointIndex > 0);
	EnableDlgItem (IDC_NEXT, g_cPointIndex + 1 < g_PtPairs.size ());

	// Locate.
	EnableDlgItem (IDC_LOCATE_POINT1, g_PtPairs [g_cPointIndex].bDefined);
	// Delete.
	EnableDlgItem (IDC_DELETE_POINT1, g_PtPairs [g_cPointIndex].bDefined);

	// Apply.
	size_t cDefined = 0;
	for (size_t cPoint = 0; cPoint < g_PtPairs.size (); ++ cPoint)
		if (g_PtPairs [cPoint].bDefined)
			++ cDefined;
	EnableDlgItem (IDC_APPLY, cDefined >= 3);

	_UpdateCursor ();
}

/////////////////////////////////

class CTransformTool : public CTool {
  public:
	CTransformTool () : CTool (toolTransform),
		m_Dlg0 (IDD_TRANSFORM_SHIFT),
		m_Dlg1 (IDD_TRANSFORM_AFFINE),  
		m_Dlg2 (IDD_TRANSFORM_QUADRATIC),
		m_DlgM ()
	{}

	virtual bool Init ();
	virtual void Destroy ();

	virtual void OnRemoveMap ();
	virtual void OnActivate ();
	virtual void OnDeactivate ();

	virtual void OnLDown (int _x, int _y, DWORD _dwKeys);
	virtual void OnLUp   (int _x, int _y, DWORD _dwKeys);
	virtual void OnMove  (int _x, int _y, DWORD _dwKeys);

	virtual void OnEsc ();
	virtual void OnSwitchSubTool ();

	virtual void OnPaint (HDC _hDC, const RECT & _rect);

  private:
	CTransformDlg m_Dlg0, m_Dlg1, m_Dlg2;
	CTransformMultipleDlg m_DlgM;
};

DECLARE_TOOL (CTransformTool);

const size_t c_cTransformations = 4;
static CTransformDlg * g_pTransformationDlg [c_cTransformations];

static
void ShowTransformDialog () {
	assert (g_cTransformationOrder < c_cTransformations);
	CTransformDlg * const pDlg = g_pTransformationDlg [g_cTransformationOrder];

	pDlg->UpdateCtrls (true);

	::SetWindowPos (* pDlg, HWND_TOP, GetSetting ("TransformX", (DWORD) 0), GetSetting ("TransformY", (DWORD) 0), 0, 0, SWP_NOSIZE);
	::ShowWindow (* pDlg, SW_SHOW);
}

static
void HideTransformDialog () {
	assert (g_cTransformationOrder < c_cTransformations);
	CTransformDlg * const pDlg = g_pTransformationDlg [g_cTransformationOrder];

	::ShowWindow (* pDlg, SW_HIDE);
}

static
void RefreshTransformDialog () {
	assert (g_cTransformationOrder < c_cTransformations);
	CTransformDlg * const pDlg = g_pTransformationDlg [g_cTransformationOrder];

	pDlg->UpdateCtrls ();
}

bool CTransformTool::Init () {
	g_PtPairs.resize (6);
	for (size_t cPoint = 0; cPoint < g_PtPairs.size (); ++ cPoint)
		g_PtPairs [cPoint].bDefined = false;

	g_hcurTransform [0] = ::LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_CursorModeAT));
	g_hcurTransform [1] = ::LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_CursorModeAT2));
	g_hcurTransform [2] = ::LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_CursorModeAT3));
	g_hcurTransform [3] = ::LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_CursorModeQT4));
	g_hcurTransform [4] = ::LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_CursorModeQT5));
	g_hcurTransform [5] = ::LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_CursorModeQT6));
	g_hcurTransformDrag = ::LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_CursorModeATDrag));

	g_hRedThinPen   = ::CreatePen (PS_SOLID, 1, c_colRed);
	g_hDkRedThinPen = ::CreatePen (PS_SOLID, 1, c_colDkRed);

	assert (g_hMapWnd);
	m_Dlg0.CreateModeless (g_hMapWnd);
	m_Dlg1.CreateModeless (g_hMapWnd);
	m_Dlg2.CreateModeless (g_hMapWnd);
	m_DlgM.CreateModeless (g_hMapWnd);

	g_pTransformationDlg [0] = & m_Dlg0;
	g_pTransformationDlg [1] = & m_Dlg1;
	g_pTransformationDlg [2] = & m_Dlg2;
	g_pTransformationDlg [3] = & m_DlgM;

	return true;
}

void CTransformTool::Destroy () {
	if (g_hRedThinPen)
		::DeleteObject (g_hRedThinPen);
	if (g_hDkRedThinPen)
		::DeleteObject (g_hDkRedThinPen);
}

void CTransformTool::OnRemoveMap () {
	g_cPointIndex = 0;

	for (size_t c = 0; c < g_PtPairs.size (); ++ c)
		g_PtPairs [c].bDefined = false;

	RefreshTransformDialog ();
}

static bool g_bTransformActive;

void CTransformTool::OnActivate () {
	g_bTransformActive = true;

	ShowTransformDialog ();

	SetPermanentCursor (g_hcurTransform [g_cPointIndex < ARRAY_SIZE (g_hcurTransform) ? g_cPointIndex : 0]);
}

void CTransformTool::OnDeactivate () {
	g_bTransformActive = false;

	HideTransformDialog ();
}

void CTransformTool::OnLDown (int _x, int _y, DWORD _dwKeys) {
	SetPermanentCursor (g_hcurTransformDrag);

	ScreenToPt (_x, _y, g_PtPairs [g_cPointIndex].ptFrom);
	g_PtPairs [g_cPointIndex].ptTo = g_PtPairs [g_cPointIndex].ptFrom;
	g_PtPairs [g_cPointIndex].bDefined = true;

	RefreshTransformDialog ();
	Redraw ();

	CTool::s_bDND = true;
	::SetCapture (g_hMapWnd);
}

void CTransformTool::OnLUp (int _x, int _y, DWORD _dwKeys) {
	SetPermanentCursor (g_hcurTransform [g_cPointIndex < ARRAY_SIZE (g_hcurTransform) ? g_cPointIndex : 0]);

	if (CTool::s_bDND) {
		CTool::s_bDND = false;
		::ReleaseCapture ();

		if (g_cPointIndex < g_PtPairs.size ())
			ScreenToPt (_x, _y, g_PtPairs [g_cPointIndex].ptTo);

		Redraw ();
	}
}

void CTransformTool::OnMove (int _x, int _y, DWORD _dwKeys) {
	if (! CTool::s_bDND)
		return;

	// TODO: dnd for points

	SetPermanentCursor (g_hcurTransformDrag);

	if (g_PtPairs [g_cPointIndex].bDefined) {
		const point_t ptOldTo = g_PtPairs [g_cPointIndex].ptTo;

		ScreenToPt (_x, _y, g_PtPairs [g_cPointIndex].ptTo);

		// Optimize update rect.
		const long lX_From  = XToScreen (g_PtPairs [g_cPointIndex].ptFrom.x);
		const long lY_From  = YToScreen (g_PtPairs [g_cPointIndex].ptFrom.y);
		const long lX_To    = XToScreen (g_PtPairs [g_cPointIndex].ptTo.x);
		const long lY_To    = YToScreen (g_PtPairs [g_cPointIndex].ptTo.y);
		const long lX_OldTo = XToScreen (ptOldTo.x);
		const long lY_OldTo = YToScreen (ptOldTo.y);

		RECT rect1 = {lX_From, lY_From, lX_From + 1, lY_From + 1};
		RECT rect2 = {lX_To    - c_AB_SIZE, lY_To    - c_AB_SIZE, lX_To    + c_AB_SIZE + 1, lY_To    + c_AB_SIZE + 1};
		RECT rect3 = {lX_OldTo - c_AB_SIZE, lY_OldTo - c_AB_SIZE, lX_OldTo + c_AB_SIZE + 1, lY_OldTo + c_AB_SIZE + 1};

		RECT rect;
		::UnionRect (& rect, & rect1, & rect2);
		::UnionRect (& rect, & rect,  & rect3);

		Redraw (& rect);
	}
}

void CTransformTool::OnEsc () {
	assert (g_cPointIndex < g_PtPairs.size ());
	g_PtPairs [g_cPointIndex].bDefined = false;

	Redraw ();
}

void CTransformTool::OnSwitchSubTool () {
	const size_t cOrder = GetTransformationOrder () + 1;
	SetTransformationOrder (cOrder <= 3 ? cOrder : 0);
}

void CTransformTool::OnPaint (HDC _hDC, const RECT & _rect) {
	if (! g_bTransformActive)
		return;

	//
	// Draw shifted semi-transparent part of map.
	//
	if (g_PtPairs [g_cPointIndex].bDefined) {
		const long lX_From = XToScreen (g_PtPairs [g_cPointIndex].ptFrom.x);
		const long lY_From = YToScreen (g_PtPairs [g_cPointIndex].ptFrom.y);
		const long lX_To   = XToScreen (g_PtPairs [g_cPointIndex].ptTo.x);
		const long lY_To   = YToScreen (g_PtPairs [g_cPointIndex].ptTo.y);

		if (lX_From != lX_To || lY_From != lY_To) {
			long w = c_AB_SIZE*2;
			long h = c_AB_SIZE*2;
			long sx = lX_From - c_AB_SIZE;
			long sy = lY_From - c_AB_SIZE;
			long dx = lX_To - c_AB_SIZE;
			long dy = lY_To - c_AB_SIZE;
			if (sx < 0) {
				dx -= sx;
				w += sx;
				sx = 0;
			} else if (sx + w >= g_lWidth)
				w = g_lWidth - sx;
			if (sy < 0) {
				dy -= sy;
				h += sy;
				sy = 0;
			} else if (sy + h >= g_lHeight)
				h = g_lHeight - sy;

			BLENDFUNCTION bf = {AC_SRC_OVER, 0, 96, 0};
			g_fpAlphaBlend (_hDC, dx, dy, w, h, g_hMemDC, sx, sy, w, h, bf);
		}
	}

	//
	// Draw arrows.
	//

	::SelectObject (_hDC, g_hFont);
	::SetBkMode (_hDC, TRANSPARENT);
	::SetTextAlign (_hDC, TA_CENTER | TA_BASELINE);

	size_t cMaxPoints = g_PtPairs.size ();
	switch (g_cTransformationOrder) {
		case 0: cMaxPoints = 1; break;
		case 1: cMaxPoints = 3; break;
		case 2: cMaxPoints = 6; break;
	}

	for (size_t cPoint = 0; cPoint < cMaxPoints; ++ cPoint) {
		if (! g_PtPairs [cPoint].bDefined)
			continue;

		::SelectObject (_hDC, cPoint == g_cPointIndex ? g_hDkRedThinPen : g_hRedThinPen);
		::SetTextColor (_hDC, cPoint == g_cPointIndex ? c_colDkRed      : c_colRed);

		const long lX_From = XToScreen (g_PtPairs [cPoint].ptFrom.x);
		const long lY_From = YToScreen (g_PtPairs [cPoint].ptFrom.y);
		const long lX_To   = XToScreen (g_PtPairs [cPoint].ptTo.x);
		const long lY_To   = YToScreen (g_PtPairs [cPoint].ptTo.y);

		if (lX_From != lX_To || lY_From != lY_To) {
			::MoveToEx (_hDC, lX_From, lY_From, NULL);
			::LineTo   (_hDC, lX_To, lY_To);

			// Draw pointer.
			const double a = ::atan2 (static_cast<double> (lY_To - lY_From), static_cast<double> (lX_To - lX_From));
			const long x1 = lX_To - fast_floor (8*::cos (+.3f - a) + .5f);
			const long y1 = lY_To + fast_floor (8*::sin (+.3f - a) + .5f);
			const long x2 = lX_To - fast_floor (8*::cos (-.3f - a) + .5f);
			const long y2 = lY_To + fast_floor (8*::sin (-.3f - a) + .5f);

			::MoveToEx (_hDC, lX_To, lY_To, NULL);
			::LineTo   (_hDC, x1, y1);

			::MoveToEx (_hDC, lX_To, lY_To, NULL);
			::LineTo   (_hDC, x2, y2);
		} else {
			// Draw cross.
			::MoveToEx (_hDC, lX_To - 5, lY_To - 5, NULL);
			::LineTo   (_hDC, lX_To + 6, lY_To + 6);

			::MoveToEx (_hDC, lX_To - 5, lY_To + 5, NULL);
			::LineTo   (_hDC, lX_To + 6, lY_To - 6);
		}

		// Draw label.
		char strBuf [10];
		::itoa (cPoint + 1, strBuf, 10);
		const float d = ::_hypot (lX_From - lX_To, lY_From - lY_To);
		const long dx = d > 0 ? 0 + fast_floor ((lY_From - lY_To)*8/d + .5f) : 1;
		const long dy = d > 0 ? 3 - fast_floor ((lX_From - lX_To)*8/d + .5f) : -5;
		::TextOut (_hDC, (lX_From + lX_To)/2 + dx, (lY_From + lY_To)/2 + dy, strBuf, 1);
	}
}

//////////////////////////////

void SetTransformationOrder (size_t _cOrder) {
	assert (_cOrder <= 3);
	if (g_cTransformationOrder == _cOrder)
		return;

	HideTransformDialog ();

	g_cTransformationOrder = _cOrder;
	switch (g_cTransformationOrder) {
		case 0: g_cPointIndex = 0; break;
		case 1: if (g_cPointIndex > 3) g_cPointIndex = 0; break;
	}

	CTool::SetPermanentCursor (g_hcurTransform [g_cPointIndex]);
	Redraw ();

	ShowTransformDialog ();
	RefreshTransformDialog ();
}

size_t GetTransformationOrder () {
	assert (g_cTransformationOrder < c_cTransformations);
	return g_cTransformationOrder;
}

